package com.ych.utils;

import com.alibaba.fastjson.JSONObject;
import com.ych.exception.UKInvalidException;
import com.ych.redis.RedisUtil;
import com.ych.redis.config.RedisKeyConfig;
import com.ych.utils.serviceReturn.R;
import com.ych.utils.token.TokenSessionEntity;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.support.WebApplicationContextUtils;

import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.Instant;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * Created with IntelliJ IDEA.
 * Author: Usopp.tsui
 * Date: 2020/12/5
 * Time: 15:13
 * Description:
 */
public class AuthInitAccessUtil {
    private final static String[] swaggerUrl = new String[]{
            "/swagger-resources",
            "/v2/api-docs",
            "/v2/api-docs-ext",
            "/doc.html",
            "/webjars"
    };

    /**
     * 检验请求是否带有用户id以及token
     *
     * @param request
     */
    public static void authUserAndToken(HttpServletRequest request) {
        String userId = request.getHeader("user_id");//从请求头获取
        if (StringUtils.isBlank(userId) || "NULL".equals(userId.toUpperCase())) {
            userId = request.getParameter("user_id");//从参数获取一次
            if (StringUtils.isBlank(userId) || "NULL".equals(userId.toUpperCase())) {
                throw new UKInvalidException("失效[U],服务端终止请求");
            }
        }
        TLMap.setUserId(userId);

        String token = request.getHeader("token");
        if (StringUtils.isBlank(token) || "NULL".equals(token.toUpperCase())) {
            token = request.getParameter("token");//从参数获取
            if (StringUtils.isBlank(token) || "NULL".equals(token.toUpperCase())) {
                throw new UKInvalidException("失效[T],服务端终止请求");
            }
        }
        TLMap.setToken(token);
    }

    /**
     * 方法针对于不鉴权接口,场景:
     * 有些不鉴权接口也会允许登录用户进行自身的ID匹配,
     * 为防止被他人冒用非自己的userId进行查询,所以将userId,token设为全局前,进行身份鉴权
     */
    public static void appendUserAndToken(HttpServletRequest request) {
        String user_id = request.getHeader("user_id");//从请求头取
        if (StringUtils.isBlank(user_id) || user_id.toUpperCase().equals("NULL")) {
            user_id = request.getParameter("user_id");//从参数获取一次
            if (StringUtils.isBlank(user_id) || user_id.toUpperCase().equals("NULL")) {
                user_id = null;
            }
        }
        String token = request.getHeader("token");
        if (StringUtils.isBlank(token) || token.toUpperCase().equals("NULL")) {
            token = request.getParameter("token");//从参数获取一次
            if (StringUtils.isBlank(token) || token.toUpperCase().equals("NULL")) {
                token = null;
            }
        }

        if (StringUtils.isNotBlank(user_id) && StringUtils.isNotBlank(token)) {
            try {
                //拥有用户id和token，则进行token校验
                tokenRedisCheck(request, user_id, token);
                TLMap.setUserId(user_id);
                TLMap.setToken(token);
            } catch (Exception e) {
                /**
                 * 此处异常不需要处理,允许登录用户对不鉴权接口进行userId查询
                 */
            }
        }
    }

    /**
     * 检查当前用户是否登录状态
     */
    public static void tokenRedisCheck(HttpServletRequest req, String userId, String token) {
        //查询token，进行比对
        BeanFactory factory = WebApplicationContextUtils.getRequiredWebApplicationContext(req.getServletContext());
        RedisUtil redisTemplate = (RedisUtil) factory.getBean("redisUtil");
        String sessionId = RedisKeyConfig.sessionToken(userId);
        TokenSessionEntity tokenSessionEntity = (TokenSessionEntity) redisTemplate.redisTemplate().opsForValue().get(sessionId);
        if (null == tokenSessionEntity) {
            throw new UKInvalidException("失效[T],服务端终止请求");
        }

        if (!tokenSessionEntity.getToken().equals(token)) {
            throw new UKInvalidException("失效[T],非法请求");
        }
    }

    public static void requestLimit(HttpServletRequest req) {
        BeanFactory factory = WebApplicationContextUtils.getRequiredWebApplicationContext(req.getServletContext());
        RedisUtil redisUtil = (RedisUtil) factory.getBean("redisUtil");
        /**
         * Nginx需要配置用户真实ip地址
         */
        String ip = IPUtils.getIpAddr(req);
        if (StringUtils.isBlank(ip) || ip.toUpperCase().equals("NULL")) {
            throw new UKInvalidException("失效[IP],服务端终止请求");
        }
        //使用redis进行防刷
        //rediskey
        String redisIpKey = RedisKeyConfig.requestLimit(ip);

        Integer ipTime = (Integer) redisUtil.redisTemplate().opsForValue().get(redisIpKey);
        if (null == ipTime) {
            redisUtil.redisTemplate().opsForValue().set(redisIpKey, 1);
            redisUtil.redisTemplate().expire(redisIpKey, 2, TimeUnit.SECONDS);
        } else {
            //2秒内,同一个ip访问服务器超过1千次,拒绝访问
            if (1000 <= ipTime) {
                throw new UKInvalidException("失效[IP],服务端终止请求,访问频率过高");
            } else {
                ipTime++;
                redisUtil.redisTemplate().opsForValue().set(redisIpKey, ipTime);
                Long time = redisUtil.redisTemplate().getExpire(redisIpKey);
                Date date = new Date(Instant.now().toEpochMilli() + time * 1000);
                redisUtil.redisTemplate().expireAt(redisIpKey, date);
            }
        }
    }


    /**
     * 通用报错返回前端
     */
    public static void responseException(ServletResponse response, Exception e) {
        if (e.getMessage().contains("Subject does not have permission")) {
            responseMSG(response, HttpStatusUtil.SC_AUTH_UNKNOWN, "该功能需要管理员授权后才能使用!");
        } else if (e.getClass().getName().contains("UKInvalidException")) {
            responseMSG(response, HttpStatusUtil.SC_SIGN_IN, e.getMessage());
        } else if (e.getClass().getName().contains("DataSourceNoFindException")) {
            responseMSG(response, HttpStatusUtil.SC_DATA_SOURCE, e.getMessage());
        } else if (e.getClass().getName().contains("ServerRequestException")) {
            responseMSG(response, HttpStatusUtil.SC_SERVER_REQUEST, e.getMessage());
        } else if (e.getClass().getName().contains("PayNotifyFindException")) {
            responseMSG(response, HttpStatusUtil.SC_PAY_NOTIFYFIND, e.getMessage());
        } else {
            responseMSG(response, HttpStatusUtil.SC_UNKNOWN, e.getMessage());
        }
    }

    private static void responseMSG(ServletResponse response, int code, String msg) {
        HttpServletResponse rq = (HttpServletResponse) response;
        JSONObject responseJSONObject = (JSONObject) JSONObject.toJSON(new R(code, msg, null));
        rq.setHeader("Access-Control-Allow-Credentials", "true");
        rq.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());
        rq.setCharacterEncoding("UTF-8");
        rq.setContentType("application/json; charset=utf-8");
        rq.setStatus(200);
        PrintWriter out = null;
        try {
            out = rq.getWriter();
            out.append(responseJSONObject.toString());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (out != null) {
                out.flush();
                out.close();
            }
        }
    }

    /**
     * swagger调试
     */
    public static boolean swaggerDebug(HttpServletRequest req) {
        Environment env = (Environment) WebApplicationContextUtils.getRequiredWebApplicationContext(req.getServletContext()).getBean("environment");
        boolean swaggerEnabled = env.getProperty("swagger.enabled", boolean.class);
        if (swaggerEnabled) {//调试已打开
            for (String s : swaggerUrl) {
                if (0 == req.getRequestURI().indexOf(s)) {
                    return true;
                }
            }
            return false;
        } else {
            return false;
        }
    }

    /**
     * 通用校验请求
     */
    public static boolean adopt(HttpServletRequest req) {
        if (req.getMethod().equals(RequestMethod.OPTIONS.name())) {
            return true;  //允许vue前端尝试请求
        } else if ("/favicon.ico".equals(req.getRequestURI())) {
            return true; //允许浏览器商标请求
        } else {
            return false;
        }
    }
}